//
// System.Drawing.Imaging.EncoderParameter.cs
//
// Author: 
//	Ravindra (rkumar@novell.com)
//  Vladimir Vukicevic (vladimir@pobox.com)
//
// (C) 2004 Novell, Inc.  http://www.novell.com
//

//
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;
using System.Text;

using System.Runtime.InteropServices;

namespace System.Drawing.Imaging {

	[StructLayout(LayoutKind.Sequential)]
	public sealed class EncoderParameter : IDisposable {

		private Encoder encoder;
		private int valuesCount;
		private EncoderParameterValueType type;
		private IntPtr valuePtr;

		internal EncoderParameter ()
		{
		}

		public EncoderParameter (Encoder encoder, byte value)
		{
			this.encoder = encoder;
			this.valuesCount = 1;
			this.type = EncoderParameterValueType.ValueTypeByte;
			this.valuePtr = Marshal.AllocHGlobal (1);
			Marshal.WriteByte (this.valuePtr, value);
		}

		public EncoderParameter (Encoder encoder, byte[] value)
		{
			this.encoder = encoder;
			this.valuesCount = value.Length;
			this.type = EncoderParameterValueType.ValueTypeByte;
			this.valuePtr = Marshal.AllocHGlobal (1 * valuesCount);
			Marshal.Copy (value, 0, this.valuePtr, valuesCount);
		}

		public EncoderParameter (Encoder encoder, short value)
		{
			this.encoder = encoder;
			this.valuesCount = 1;
			this.type = EncoderParameterValueType.ValueTypeShort;
			this.valuePtr = Marshal.AllocHGlobal (2);
			Marshal.WriteInt16 (this.valuePtr, value);
		}

		public EncoderParameter (Encoder encoder, short[] value)
		{
			this.encoder = encoder;
			this.valuesCount = value.Length;
			this.type = EncoderParameterValueType.ValueTypeShort;
			this.valuePtr = Marshal.AllocHGlobal (2 * valuesCount);
			Marshal.Copy (value, 0, this.valuePtr, valuesCount);
		}


		public EncoderParameter (Encoder encoder, long value)
		{
			this.encoder = encoder;
			this.valuesCount = 1;
			this.type = EncoderParameterValueType.ValueTypeLong;
			this.valuePtr = Marshal.AllocHGlobal (4);
			Marshal.WriteInt32 (this.valuePtr, (int) value);
		}

		public EncoderParameter (Encoder encoder, long[] value)
		{
			this.encoder = encoder;
			this.valuesCount = value.Length;
			this.type = EncoderParameterValueType.ValueTypeLong;
			this.valuePtr = Marshal.AllocHGlobal (4 * valuesCount);
			int [] ivals = new int[value.Length];
			for (int i = 0; i < value.Length; i++) ivals[i] = (int) value[i];
			Marshal.Copy (ivals, 0, this.valuePtr, valuesCount);
		}

		public EncoderParameter (Encoder encoder, string value)
		{
			this.encoder = encoder;

			ASCIIEncoding ascii = new ASCIIEncoding ();
			int asciiByteCount = ascii.GetByteCount (value);
			byte[] bytes = new byte [asciiByteCount];
			ascii.GetBytes (value, 0, value.Length, bytes, 0);

			this.valuesCount = bytes.Length;
			this.type = EncoderParameterValueType.ValueTypeAscii;
			this.valuePtr = Marshal.AllocHGlobal (valuesCount);
			Marshal.Copy (bytes, 0, this.valuePtr, valuesCount);
		}

		public EncoderParameter (Encoder encoder, byte value, bool undefined)
		{
			this.encoder = encoder;
			this.valuesCount = 1;
			if (undefined)
				this.type = EncoderParameterValueType.ValueTypeUndefined;
			else
				this.type = EncoderParameterValueType.ValueTypeByte;
			this.valuePtr = Marshal.AllocHGlobal (1);
			Marshal.WriteByte (this.valuePtr, value);
		}

		public EncoderParameter (Encoder encoder, byte[] value, bool undefined)
		{
			this.encoder = encoder;
			this.valuesCount = value.Length;
			if (undefined)
				this.type = EncoderParameterValueType.ValueTypeUndefined;
			else
				this.type = EncoderParameterValueType.ValueTypeByte;
			this.valuePtr = Marshal.AllocHGlobal (valuesCount);
			Marshal.Copy (value, 0, this.valuePtr, valuesCount);
		}

		public EncoderParameter (Encoder encoder, int numerator, int denominator)
		{
			this.encoder = encoder;
			this.valuesCount = 1;
			this.type = EncoderParameterValueType.ValueTypeRational;
			this.valuePtr = Marshal.AllocHGlobal (8);
			int [] valuearray = { numerator, denominator };
			Marshal.Copy (valuearray, 0, this.valuePtr, valuearray.Length);
		}

		public EncoderParameter (Encoder encoder, int[] numerator, int[] denominator)
		{
			if (numerator.Length != denominator.Length)
				throw new ArgumentException ("Invalid parameter used.");

			this.encoder = encoder;
			this.valuesCount = numerator.Length;
			this.type = EncoderParameterValueType.ValueTypeRational;
			this.valuePtr = Marshal.AllocHGlobal (4 * valuesCount * 2);
			for (int i = 0; i < valuesCount; i++) {
				Marshal.WriteInt32 (valuePtr, i * 4, (int) numerator[i]);
				Marshal.WriteInt32 (valuePtr, (i + 1) * 4, (int) denominator[i]);
			}
		}

		public EncoderParameter (Encoder encoder, long rangebegin, long rangeend)
		{
			this.encoder = encoder;
			this.valuesCount = 1;
			this.type = EncoderParameterValueType.ValueTypeLongRange;
			this.valuePtr = Marshal.AllocHGlobal (8);
			int [] valuearray = { (int) rangebegin, (int) rangeend };
			Marshal.Copy (valuearray, 0, this.valuePtr, valuearray.Length);
		}

		public EncoderParameter (Encoder encoder, long[] rangebegin, long[] rangeend)
		{
			if (rangebegin.Length != rangeend.Length)
				throw new ArgumentException ("Invalid parameter used.");

			this.encoder = encoder;
			this.valuesCount = rangebegin.Length;
			this.type = EncoderParameterValueType.ValueTypeLongRange;

			this.valuePtr = Marshal.AllocHGlobal (4 * valuesCount * 2);
			IntPtr dest = this.valuePtr;
			for (int i = 0; i < valuesCount; i++) {
				Marshal.WriteInt32 (dest, i * 4, (int) rangebegin[i]);
				Marshal.WriteInt32 (dest, (i + 1) * 4, (int) rangeend[i]);
			}
		}

		public EncoderParameter (Encoder encoder, int numberOfValues, int type, int value)
		{
			this.encoder = encoder;
			this.valuePtr = (IntPtr) value;
			this.valuesCount = numberOfValues;
			this.type = (EncoderParameterValueType) type;
		}

		public EncoderParameter (Encoder encoder, int numerator1, int denominator1, int numerator2, int denominator2)
		{
			this.encoder = encoder;
			this.valuesCount = 1;
			this.type = EncoderParameterValueType.ValueTypeRationalRange;
			this.valuePtr = Marshal.AllocHGlobal (4 * 4);
			int [] valuearray = { numerator1, denominator1, numerator2, denominator2 };
			Marshal.Copy (valuearray, 0, this.valuePtr, 4);
		}

		public EncoderParameter (Encoder encoder, int[] numerator1, int[] denominator1, int[] numerator2, int[] denominator2)
		{
			if (numerator1.Length != denominator1.Length ||
			    numerator2.Length != denominator2.Length ||
			    numerator1.Length != numerator2.Length)
				throw new ArgumentException ("Invalid parameter used.");

			this.encoder = encoder;
			this.valuesCount = numerator1.Length;
			this.type = EncoderParameterValueType.ValueTypeRationalRange;

			this.valuePtr = Marshal.AllocHGlobal (4 * valuesCount * 4);
			IntPtr dest = this.valuePtr;
			for (int i = 0; i < valuesCount; i++) {
				Marshal.WriteInt32 (dest, i * 4, numerator1[i]);
				Marshal.WriteInt32 (dest, (i + 1) * 4, denominator1[i]);
				Marshal.WriteInt32 (dest, (i + 2) * 4, numerator2[i]);
				Marshal.WriteInt32 (dest, (i + 3) * 4, denominator2[i]);
			}
		}

		public Encoder Encoder {
			get {
				return encoder;
			}

			set {
				encoder = value;
			}
		}

		public int NumberOfValues {
			get {
				return valuesCount;
			}
		}

		public EncoderParameterValueType Type {
			get {
				return type;
			}
		}

		public EncoderParameterValueType ValueType {
			get {
				return type;
			}
		}

		void Dispose (bool disposing) {
			if (valuePtr != IntPtr.Zero) {
				Marshal.FreeHGlobal (valuePtr);
				valuePtr = IntPtr.Zero;
			}
		}

		public void Dispose () {
			Dispose (true);		
			GC.SuppressFinalize(this);
		}

		~EncoderParameter () {
			Dispose (false);
		}

		internal static int NativeSize () {
			return Marshal.SizeOf (typeof(GdipEncoderParameter));
		}

		internal void ToNativePtr (IntPtr epPtr) {
			GdipEncoderParameter ep = new GdipEncoderParameter ();
			ep.guid = this.encoder.Guid;
			ep.numberOfValues = (uint) this.valuesCount;
			ep.type = this.type;
			ep.value = this.valuePtr;
			Marshal.StructureToPtr (ep, epPtr, false);
		}

		internal static EncoderParameter FromNativePtr (IntPtr epPtr) {
			GdipEncoderParameter ep;
			ep = (GdipEncoderParameter) Marshal.PtrToStructure (epPtr, typeof(GdipEncoderParameter));

			Type valType;
			uint valCount;

			switch (ep.type) {
			case EncoderParameterValueType.ValueTypeAscii:
			case EncoderParameterValueType.ValueTypeByte:
			case EncoderParameterValueType.ValueTypeUndefined:
				valType = typeof(byte);
				valCount = ep.numberOfValues;
				break;
			case EncoderParameterValueType.ValueTypeShort:
				valType = typeof(short);
				valCount = ep.numberOfValues;
				break;
			case EncoderParameterValueType.ValueTypeLong:
				valType = typeof(int);
				valCount = ep.numberOfValues;
				break;
			case EncoderParameterValueType.ValueTypeLongRange:
			case EncoderParameterValueType.ValueTypeRational:
				valType = typeof(int);
				valCount = ep.numberOfValues * 2;
				break;
			case EncoderParameterValueType.ValueTypeRationalRange:
				valType = typeof(int);
				valCount = ep.numberOfValues * 4;
				break;
			default:
				return null;
			}

			EncoderParameter eparam = new EncoderParameter();
			eparam.encoder = new Encoder(ep.guid);
			eparam.valuesCount = (int) ep.numberOfValues;
			eparam.type = ep.type;
			eparam.valuePtr = Marshal.AllocHGlobal ((int)(valCount * Marshal.SizeOf(valType)));

			/* There's nothing in Marshal to do a memcpy() between two IntPtrs.  This sucks. */
			unsafe {
				byte *s = (byte *) ep.value;
				byte *d = (byte *) eparam.valuePtr;
				for (int i = 0; i < valCount * Marshal.SizeOf(valType); i++)
					*d++ = *s++;
			}

			return eparam;
		}
	}
}
